perm filename INCMP.MAC[IP,SYS] blob sn#680191 filedate 1982-10-14 generic text, type T, neo UTF8
;CWL:<403-INET>INCMP.MAC.40303 25-Apr-82 13:30:04, Edit by CLYNN
; Increased MAXGWA, removed MAXROU, allow "." between address bytes
; Added PURGEs, use partial gateway if too many addresses,
; Error if no gateway type, set IP seg id & ICMP seq fields
; Save TCB & call PRNPKT in ICMDSP, add timestamp & information requests
; Pass parameter problem pointer to ICMERR in T2

	SEARCH	INPAR,PROLOG
IFN MNET,<
	SEARCH	MNTPAR
>
	TTITLE	INCMP
	SUBTTL	Internet Control Message Protocol, Dan Tappan, 1/82
	SWAPCD

COMMENT	!
	These routines impliment the Internet Control Message
	Protocal. They are derived from the old GGP routines,
	which protocal we no longer support.
	Besides handling the protocal messages this module also
	maintains the gateway tables (as distinguished from the
	routing tables).

Routines in this module

ICMINI...... Initialize the ICMP handler*
GWYINI...... Initialize the gateway tables*
LODFIL...... Load the gateway file
PRCLIN...... Process lines in the gateway file
LOADGW...... Load a gateway descriptor
DEFGWY...... Create gateway blocks from a gateway descriptor

ICMPRC...... Top level ICMP process.*
ICMDSP...... Process a single ICMP message*

ICMERR...... Send an ICMP error message*

	!

; Accumulators used globally in this module:

DEFAC(GW,BFR)			; Points to a gateway block
DEFAC(CPKT,TPKT)		; Index register to point to ICMP pkt


; Parameters:

MAXGWA==↑D50			; Number of GWs we will keep track of
				; (Gateways and multi-homed hosts)
;MAXROU==↑D40			; Number of routing names

; The file name to use:

IFKA <	GWFILE:	ASCIZ "<SYSTEM>INTERNET.GATEWAYS">
IFNKA <	GWFILE:	ASCIZ "SYSTEM:INTERNET.GATEWAYS">



;;; ICMP packet is pointed to by CPKT, structure as defined in
;;; INPAR

MINICW==PKTELI+<<MINIHS+3>/4>+2	; Minimum ICMP packet size, words with local
MINIHB==<MINICW-PKTELI>*4	; Usual header size, w/o imbedded pkt


; Structure of a Gateway block:

DEFSTR(GWUP,0,0,1)		; Gateway should be useable
DEFSTR(GWPIP,0,1,1)		; Ping in progress
DEFSTR(GWTYP,0,5,4)		; Gateway type
  GW%PRM==1			; Prime: Speaks ICMP
  GW%DUM==2			; Dumb: Fwd's pkts, but that's all
  GW%HST==3			; Host: Avoid except in emergency
  GW%AUP==4			; Always-up: Fwds but doesn't reflect
DEFSTR(GWHIS,0,14,7)		; Ping history bits
DEFSTR(GWSPC,0,17,3)		; Successful ping count
  .THRUP==<6*WID(GWHIS)>/8	; Threshold to say it is up
  .THRDN==<3*WID(GWHIS)>/8	; Threshold to say it is down
  IFL <<1←WID(GWSPC)>-1>-WID(GWHIS), PRINTX ?GWSPC too small
DEFSTR(GWICT,0,35,18)		; Interface count
.GWILS==1			; Where in block to find the list
	;NB: The FIRST ENTRY in the list is on a connected net
MXGWIC==6			; Maximum number of interfaces

GWBKSZ==.GWILS+MXGWIC		; Size of a gateway block


; Tables kept and used by ICMP

;GWTAB is a list of pointers to gateway blocks that we know about.

;NR(GWTAB,1)		; Pointer to the table

;ICMINI		Initialize ICM Protocol

;
;	CALL ICMINI
;Ret+1:	Always.

ICMINI::SETZM PINGTM		; Do Pings now
	SETZM ICMTIM		; Run ICMP now

	MOVE T1,NETHT0		; Get hash table clear interval
	ADD T1,TODCLK		; add to now
	MOVEM T1,NETHTM		; when to clear them again

	SKIPE ICMIPQ		; Already have a queue head?
	 JRST ICMIN0		; Yes.

	MOVEI T1,QSZ		; Size of a queue head
	CALL GETBLK		; Get one from free area
	JUMPE T1,[INBUG(HLT,<No storage for ICMP>,ICMNST)]
	MOVEM T1,ICMIPQ		; Put where we can find it
	CALL INITQ		; Initialize it
ICMIN0:
	SETOM ICMON		; Turn the protocal on
;	CALLRET GWYINI		; Init gateways and return
; Fall into GWYINI

;;; GWYINI -- Initialize the gateway tables.
;;; Called via
;;;	CALL GWYINI
;;; Rets +1 always

GWYINI::SE1CAL			; In case called from MDDT
	ACVAR <I>
	PUSH P,GW		; Save an AC	
	SKIPN GWTAB		; Is this a reinit?
	 JRST GWYIN2		; No.
; Reinit
	MOVSI I,-MAXGWA		; Set to scan GWTAB
GWYINL:	MOVE GW,GWTAB
	ADDI GW,0(I)		; Point to actual entry
	SETZ T1,		; Get a zero
	EXCH T1,0(GW)		; Flush entry, get previous value
	SKIPN T1		; Was there one?
	 JRST GWYIN1		; No, continue
;;; Signal that all these gateways are down
	PUSH P,T1		; Save the block
	MOVE T1,.GWILS(T1)	; get local address of this gateway
	CALL GWYDWN		; Signal that it's gone away
	POP P,T1		; restore block address
	CALL RETBLK		; Yes.  Give back storage
GWYIN1:	AOBJN I,GWYINL		; Do all GW blocks
	JRST GWYIN4

; First time init
GWYIN2:	MOVEI T1,MAXGWA		; Maximum number of gateways
	CALL GETBLK		; Get a block of storage
	JUMPE T1,GWYIN9		; Crash
	MOVEM T1,GWTAB
	MOVEI T2,MAXGWA		; Size of the block
	CALL CLRBLK		; Clear it out
GWYIN4:

; Each call
GWYIN5:	CALL LODFIL		; Load the gateway file
	CALL NETHSI		; Clear the gateway cache
	CALL PINGER		; Ping the gateways
	JRST GWYINX

GWYIN9:	INBUG (HLT,<GWYINI: Crucial storage missing>,INGGP0)
GWYINX:	POP P,GW
	RET

	PURGE I

; LODFIL()	Load the gateway file

;
;	CALL LODFIL
;Ret+1:	Always

LODFIL:	ACVAR <JFH,CHNS>
	SETO JFH,		; Indicate nothing to release
	MOVEI T1,.FHSLF		; This fork
	RCM			; Get channels which are on
	MOVEM T1,CHNS		; Save for restoring
	MOVEI T1,.FHSLF
	MOVX T2,1B<.ICEOF>	; End of file channel
	DIC			; Prevent unwanted interrupt
	MOVX T1,GJ%OLD+GJ%SHT	; Want existing file
	HRROI T2,GWFILE		; Pointer to filename string
	GTJFN
	 JRST LODFIX		; Not there
	MOVEM T1,JFH
	MOVX T2,<FLD(7,OF%BSZ)+OF%RD> ; Want to read it
	OPENF
	 JRST LODFIX
	CALL PRCLIN		; Process lines in the file
	CLOSF
	 JFCL
LODFIX:	SKIPL T1,JFH
	 RLJFN
	  JFCL
	MOVEI T1,.FHSLF
	MOVE T2,CHNS
	AIC
	RET

	PURGE JFH,CHNS

; PRCLIN(JFH)	Process lines of the gateway file

;T1/	JFH of the file
;
;	CALL PRCLIN
;Ret+1:	Always.  T1 still has the JFH

PRCLIN::ACVAR <JFH,BOL,ERRPNT,ERRCOL>
	MOVEM T1,JFH		; Stash JFH in a save place

; Top of main per-line loop:

PRCLI1:	MOVE T1,JFH		; Get the file JFH
	RFPTR			; Find out where in file line is
	 JFCL
	MOVEM T2,BOL		; Save beginning of line

	CALL GETC		; First character of line
	JUMPE T2,PRCLIX		; get out if end of file
	CAIN T2,12		; Linefeed?
	 JRST PRCLI1		; Ignore blank lines
	CAIN T2,";"
	 JRST PRCLI8		; Flush comment line
	CAIN T2,"C"
	 JRST PRCLI7		; Go do CREATION command
	BKJFN			; Back up so LOADGW can read 1st chr
	 JFCL			; Will ITRAP on BIN if error in T1
	CALL LOADGW		; Load a gateway description
	JUMPE T2,PRCLI1		; Do next if no error

; Here when error detected in current line (pointer to message in T2)

PRCLI2:	MOVEI T1,.PRIOU
	SETZ T3,
	SOUT			; Type the error string
	HRROI T2,[ASCIZ " in file: "]
	SOUT
	MOVE T2,JFH
	JFNS			; And the actual file name
	HRROI T2,[ASCIZ "
"]
	SOUT			; And a carriage return

	MOVE T1,JFH
	RFPTR			; Find out where we have read to
	 JFCL
	SOS ERRPNT,T2		; Save the error point
	MOVE T2,BOL		; Beginning of the bad line
	SFPTR
	 JFCL
	SETOM ERRCOL		; Maybe nothing read of line

; Top of loop that types out a bad line

PRCLI3:	MOVE T1,JFH
	RFPTR			; Get the file pointer
	 JFCL
	CAME T2,ERRPNT		; Up to the point of the error
	 JRST PRCLI4		; No.  Dont save column yet
	MOVEI T1,.PRIOU
	RFPOS
	HRRZM T2,ERRCOL		; Column where to show error
PRCLI4:	MOVE T1,JFH
	CALL GETC		; Get a character from bad line
	SKIPN T2		; End of file?
	 MOVEI T2,12		; Yes.  Use linefeed.
	CAIN T2,12		; End of line?
	 JRST PRCLI5		; Yes. Done
	MOVEI T1,.PRIOU
	BOUT			; Type a character
	JRST PRCLI3		; Do next one

PRCLI5:	MOVEI T1,.PRIOU
	HRROI T2,[ASCIZ "
"]
	SETZ T3,
	SOUT			; Type and end of line terminal
	JUMPLE ERRCOL,PRCLI6	; Know where to show the error?
	MOVEI T2," "		; Yes.  Space over to it.
	BOUT
	SOJG ERRCOL,.-1		; All the way.
PRCLI6:	HRROI T2,[ASCIZ "↑
"]
	SOUT
	JRST PRCLI1		; Try to finish the file

; Do CREATION command

PRCLI7:	CALL GETC		; Skip over stuff following the C
	MOVE T3,T2		; Free up T2
	HRROI T2,[ASCIZ "% INCMP: Premature EOF"]
	JUMPE T3,PRCLI2		; Go do the error if need be
	CAIE T3," "		; One space is required separator
	 JRST PRCLI7		; Loop til it is found
	SETZ T2,		; Default flags
	IDTIM			; Input the time and date
	 SKIPA T2,[-1,,[ASCIZ "% INCMP: Bad format in creation date"]]
	 MOVEM T2,GFCTAD	; Save our gateway file creation date
	JUMPL T2,PRCLI2		; Do error if need be
	JRST PRCLI1		; Do another command

; Here to flush a comment line

PRCLI8:	CALL GETC		; Get a character
	JUMPE T2,PRCLIX		; Get out if end of file
	CAIE T2,12		; End of line?
	 JRST PRCLI8		; No.
	JRST PRCLI1		; Go read the next line.

PRCLIX:	MOVE T1,JFH		; Preserve JFH as promised
	RET

	PURGE JFH,BOL,ERRPNT,ERRCOL

NR GFCTAD,1			; Gateway file creation time and date

; LOADGW(JFH)		Load one gateway desciption and add to table

;T1/	JFH
;
;	CALL LOADGW
;Ret+1:	Always. T2 has 0 if no error or -1,,errorstring
;		T1 preserved.

LOADGW:	ACVAR <JFH,EOLFLG>
	TRVAR <<GWTMP,GWBKSZ>> ; Temp gateway block storage
	PUSH P,GW
	MOVEM T1,JFH

	XMOVEI T1,GWTMP		; Point to the temp block
	MOVE GW,T1		; ...
	MOVEI T2,GWBKSZ		; size thereof
	CALL CLRBLK		; clear it
	SETZM EOLFLG		; end of line not seen

; Top of per-keyword loop:

LOADG1:	MOVE T1,JFH
	CALL GETC		; Get a character
	JUMPE T2,LOADG8		; Oops.  End of file.
	CAIE T2,"."		; decimal number separator
	 CAIN T2," "		; Space (control, etc)
	  JRST LOADG1		; Yes.  Flush it.
	CAIL T2,"0"
	 CAILE T2,"9"
	  JRST LOADG4		; Non-digit.  Must be keyword

; Here to input an interface address in  N H L I form.

LOADG2:	SETZM T4		; Clear the number accumulator
	BKJFN			; Reread the digit
	 JFCL
LODG2A:	MOVEI T3,↑D10		; Decimal
	NIN
	 JRST LOADG7		; Null number?
	LSH T4,↑D8		; Make room for another byte
	ADD T4,T2		; Add it in
	BKJFN			; Reread the terminator
	 JFCL
	BIN
	CAIN T2,15		; Happens on TENEX
	 BIN			; Get the line feed, like TOPS20
	JUMPE T2,LOADG8		; Jump if end of file encountered
	CAIE T2,"."		; Dots separate bytes
	 CAIN T2," "		; Space means another byte follows
	  JRST LODG2A		; Go get it
	CAIN T2,12		; End of line?
	 SETOM EOLFLG		; Yes.  Remember to exit later.
	CAIE T2,12		; End of line
	 CAIN T2,","		; End of address expression?
	  JRST LOADG3		; Yes.  Go enter into GW block
	JRST LOADG7		; Anything else is bad format.

; Put address in temporary GW block.

LOADG3:	LOAD T3,GWICT,(GW)	; Get current count
	CAIL T3,MXGWIC		; Room for another?
	 JRST LOAD65		; No.
	ADDI T3,1		; Bump the count
	STOR T3,GWICT,(GW)	; Store back
	ADDI T3,.GWILS-1	; Offset to first empty slot
	ADD T3,GW		; Where to store the address
	MOVEM T4,0(T3)		; Insert interface address into GW block
	SKIPN EOLFLG		; Read entire GW spec?
	 JRST LOADG1		; No.  Get another keyword/addr
	JRST LOADG6		; Yes.  Go tie off this block

; Process a keyword

LOADG4:	SETO T3,		; Keyword error flag
	CAIN T2,"P"		; "PRIME"
	 MOVX T3,GW%PRM
	CAIN T2,"D"		; "DUMB"
	 MOVX T3,GW%DUM
	CAIN T2,"H"		; "HOST"
	 MOVX T3,GW%HST
	CAIN T2,"A"
	 MOVX T3,GW%AUP		; "ALLWAYS-UP"
	HRROI T2,[ASCIZ "% LOADGW: Unknown keyword "]
	JUMPL T3,LOADGX		; Give error if invalid keyword
	HRROI T2,[ASCIZ "% LOADGW: Too many gateway type specs."]
	JN GWTYP,(GW),LOADGX	; Give error if already have spec
	STOR T3,GWTYP,(GW)	; Set type into GW block

; Here to skip over the rest of the current keyword

LOADG5:	CALL GETC		; Get a character
	JUMPE T2,LOADG8		; End of file?
	CAIN T2,12		; End of line?
	 JRST LOADG6		; Yes.  Go tie it off.
	CAIE T2," "		; Space
	CAIN T2,","		; Or comma will end it
	 JRST LOADG1		; Go read next keyword
	JRST LOADG5		; Keep reading the rest of this one

; Here to tie off the block which has been accumulating

LOADG6:	CALL DEFGWY		; Create real gateway blocks
	JRST LOADGX		; return with the result

; Error returns

LOAD65:	CALL DEFGWY		; Create real gateway blocks
	SKIPN T2		; Double error
	  HRROI T2,[ASCIZ /% INCMP: Too many addresses in gateway description./]
	JRST LOADGX		; return with the result

LOADG7:	SKIPA T2,[-1,,[ASCIZ "% INGGP: Bad format "]]
LOADG8:	HRROI T2,[ASCIZ "% INGGP: Premature end of file "]

LOADGX:	MOVE T1,JFH
	POP P,GW
	RET

	PURGE JFH,EOLFLG,GWTMP

; DEFGWY (GW) -- Create real gateway blocks
; given a gateway block pointer in GW, creates a real gateway block
; for each interface on a network we have in common with the gateway
;
; GW/	(ext) pointer to gateway block (in stack)
;	CALL DEFGWY
;Ret+1:	Always, T2/	0 if ok, or
;			-1,,pointer to error msg

DEFGWY:	ACVAR <CIDX,CCNT,CSLT>
	XMOVEI CIDX,.GWILS(GW)	; Point to the interface list
	LOAD CCNT,GWICT,(GW)	; Interface count
	JUMPE CCNT,DEFGX1	; None (?)
	JE GWTYP,(GW),DEFGX2	; No type specified

;; Top of the loop

DEFGW0:	MOVE T1,(CIDX)		; Get an interface from the table
	CALL LCLNET		; have we an interface on the same net?
	 JRST DEFGW9		; no

;;; Find a slot to store the gateway block in

	MOVE CSLT,GWTAB		; point to the gateway table
	MOVEI T4,MAXGWA		; size of the table
DEFGW1:	SKIPN 0(CSLT)		; slot empty?
	 JRST DEFGW2		; yes
	XMOVEI CSLT,1(CSLT)	; increment pointer
	SOJG T4,DEFGW1		; Loop
	HRROI T2,[ASCIZ /% INCMP: DEFGW -- GWTAB full/]
	RET

DEFGW2:	MOVEI T1,GWBKSZ		; size of a gateway block
	CALL GETBLK		; get storage
	JUMPE T1,DEFGX3		; no storage
	MOVEI T2,GWBKSZ		; Size of the block
	PUSH P,T1		; Save block address
	CALL CLRBLK		; clear it
	POP P,T1		; get block address back
	SETONE <GWUP,GWHIS>,(T1) ; Init history bits
	MOVEI T2,WID(GWHIS)	; Number of bits in ping history
	STOR T2,GWSPC,(T1)	; Set succesfull ping count to match
	LOAD T2,GWTYP,(GW)	; Get gateway type
	STOR T2,GWTYP,(T1)	; And save it
	MOVE T2,(CIDX)		; Get interface we can reach
	MOVEM T2,.GWILS(T1)	; Save

	LOAD T3,GWICT,(GW)	; interface count
	STOR T3,GWICT,(T1)	; Save here also

	XMOVEI T2,.GWILS(GW)	; point to the list
	PUSH P,T1		; Save block
	XMOVEI T1,.GWILS+1(T1)	; Point to interface list
DEFGW3:	CAMN CIDX,T2		; Same as current?
	 JRST DEFGW4		; yes, on to next
	MOVE T4,(T2)		; get an interface
	MOVEM T4,(T1)		; save in block
	XMOVEI T1,1(T1)		; increment block pointer
DEFGW4:	XMOVEI T2,1(T2)		; increment source pointer
	SOJG T3,DEFGW3		; loop
	POP P,T1		; restore block pointer
	MOVEM T1,(CSLT)		; save block in gateway table

; See if another interface on a common net

DEFGW9:	XMOVEI CIDX,1(CIDX)	; increment interface pointer
	SOJG CCNT,DEFGW0	; try the next interface
	SETZ T2,		; return good
	RET			; return when done

;;; Error returns

DEFGX1:	HRROI T2,[ASCIZ /% INCMP: DEFGW -- No interfaces for gateway/]
	RET

DEFGX2:	HRROI T2,[ASCIZ /% INCMP: No gateway type specified/]
	RET

DEFGX3:	HRROI T2,[ASCIZ /% INCMP: DEFGW -- No free storage for gateway block/]
	RET

	PURGE	CIDX,CCNT,CSLT

; GETC(JFH)	Get a character from a file

;T1/	JFH of the file
;
;	CALL GETC
;Ret+1:	T1 preserved.  T2 has the chr or 0 if end of file


GETC:	BIN			; Read the file
	JUMPN T2,GETC2		; Jump if a character gotten
	GTSTS			; Read a null.
	TXNN T2,GS%EOF		; At end of file?
	 JRST GETC		; No.  Just flush the null
	MOVEI T2,0		; Set to return the EOF code
	JRST GETCX

GETC2:	CAIE T2,14		; Formfeed?
	CAIN T2,37		; TENEX EOL?
	 MOVEI T2,12		; Convert to linefeed
	CAIN T2,12		; Linefeed?
	 JRST GETCX		; Return that
	CAIGE T2," "		; Other control?
	 JRST GETC		; Yes.  Flush
	CAIL T2,"a"
	CAILE T2,"z"
	 CAIA			; Not lowercase
	 SUBI T2,"a"-"A"	; Raise lowercase
GETCX:	RET

;;; Linkage to the routing routines

;;; FNDGWY -- Find a gateway with an interface on a given net.
;;; Called
;;;	T1/	HOST number
;;; returns
;;;	+1 always
;;;	T1/	address of the best gateway to that net
;;;	(if none directly connected can be found, then a random
;;;	PRIME gateway is chosen)
;;;	If no gateways or interfaces are up returns 0


FNDGWY::ACVAR	<GWT,I,DEFGW>
	SETZ	DEFGW,			; No default gateway yet
	MOVSI	I,-MAXGWA		; Size of tables
	NETNUM	T2,T1			; Get the network number
FNDGWL:	HRRZ	GWT,I			; Get offset
	ADD	GWT,GWTAB		; Point into table
	SKIPN	GWT,(GWT)		; Get entry (if any)
	 JRST	FNDGW5			; Slot is empty
	JE	GWUP,(GWT),FNDGW5 	; Gateway is not up
	MOVE	T1,.GWILS(GWT)		; Get accessable address
	CALL	NETCHK			; Is this interface up?
	 JRST	FNDGW5			; No, try another gateway
	JUMPN	DEFGW,FNDGW0		; If we already have a default
	LOAD	T3,GWTYP,(GWT)		; Get type
	CAIN	T3,GW%PRM		; PRIME?
	 MOVE	DEFGW,.GWILS(GWT)	; Get the accessable address
FNDGW0:	LOAD	T3,GWICT,(GWT)		; Get the interface count
	XMOVEI	T4,.GWILS(GWT)		; Point to interface names
FNDGW1:	MOVE	T1,(T4)			; Get an address
	NETNUM	T1,T1			; Get the net number
	CAME	T1,T2			; Same network as we want?
	 JRST	FNDGW2			; No
	MOVE	T1,.GWILS(GWT)		; Get the accessable address
	RET				; and return

FNDGW2:	AOS	T4			; Point to the next entry
	SOJG	T3,FNDGW1		; and loop through this gateway
FNDGW5:	AOBJN	I,FNDGWL		; Loop through all gateway blocks
;;; Here if no gateway is perfect
	MOVE	T1,DEFGW		; get default gateway (0 if none)
	RET				; no gateway found
	PURGE	GWT,I,DEFGW

; Top level ICMP Processing routine.  Called from main Internet
; fork loop.
; (no args)
;
;	CALL ICMPRC
;Ret+1:	Always

ICMPRC::SETZM ICMFLG		; Clear run request flag

	CALL ICMDSP		; Dispatch any msgs which are waiting

	MOVE T1,PINGTM		; Time of next ping
	CAMGE T1,TODCLK		; Over due?
	  CALL PINGER		; Yes.  Do ping stuff.

	MOVE T1,NETHTM		; Time to reinit the hash tables?
	CAML T1,TODCLK		; ?
	  JRST ICMPR1		; No, skip following
	CALL NETHSI		; clear the tables
	MOVE T1,TODCLK		; get time now
	ADD T1,NETHT0		; add in offset
	MOVEM T1,NETHTM		; save
ICMPR1:
	CAML T1,PINGTM		; use the minimum time
	  MOVE T1,PINGTM	; get time of next ping
	MOVEM T1,ICMTIM		; save as when we have to run
	RET




;  Check Routine for ICMP Tells when to run next

;T1/	A TODCLK
;
;	CALL ICMCHK
;Ret+1:	Always.  T1 has min of input T1 and when we should run next.

ICMCHK::CAMLE T1,ICMTIM		; Check against our next timeout
	 MOVE T1,ICMTIM		; That is sooner
	RET

; PINGER()	Ping gateways to see if they are alive

;
;	CALL PINGER
;Ret+1:	Always.  PINGTM reset for next run

PINGER:	ACVAR <I>
	PUSH P,GW
	MOVSI I,-MAXGWA		; Set to scan the gateway table
PINGE1:	HRRZ GW,I		; Get offset into table
	ADD GW,GWTAB		; Add base pointer
;;	SKIPN INTSCR		; No pings if secure
	SKIPN GW,0(GW)		; Get pointer to gateway
	 JRST PINGE8		; Unoccupied slot
	LOAD T1,GWHIS,(GW)	; Get the history bits
	LOAD T2,GWSPC,(GW)	; Get the successful ping count
	TRNE T1,1		; Test bit about to be forgotten
	 SUBI T2,1		; Forgetting a success
	SKIPGE T2		; Avoid negative while down
	 MOVEI T2,0		; This is as bad as you can get
	LOAD T3,GWPIP,(GW)	; See if previous ping still in progress
	XORI T3,1		; Flip sense to indicate success
	LSH T3,WID(GWHIS)-1	; Move to left end
	LSH T1,-1		; Flush the oldest history bit
	IOR T1,T3		; Include in history bits
	SKIPE T3		; Did we add a success to the list
	 ADDI T2,1		; Yes.  Count it up
	CAILE T2,WID(GWHIS)	; Check for overflow
	 MOVEI T2,WID(GWHIS)	; Limit to max
	STOR T2,GWSPC,(GW)	; Store back the count
	STOR T1,GWHIS,(GW)	; Store back the bits
	LOAD T4,GWUP,(GW)	; Get current state
	MOVE T3,T4		; Save a copy
	CAIL T2,.THRUP		; Enough success to say it's up?
	 MOVEI T4,1		; Yes
	CAIG T2,.THRDN		; So few that it is down?
	 MOVEI T4,0		; Yes.
	STOR T4,GWUP,(GW)	; Set new value
	XOR T3,T4		; Compare to see if change
	JUMPE T3,PINGE7		; Jump if no change
	JUMPN T4,PINGE7		; Jump if it came up
	CALL GWDOWN		; Yes.  Flush from tables now.
PINGE7:	SETONE GWPIP,(GW)	; Set ping-in-progress bit
	CALL SNDPNG		; Send a ping to guy in GW
PINGE8:	AOBJN I,PINGE1		; Loop over all gateways
	MOVE T1,PINGT0		; Interping interval
	ADD T1,TODCLK		; Time of next ping/check
	MOVEM T1,PINGTM		; Save for scheduling
	POP P,GW
	RET

	PURGE I

;SNDPNG		Send a ping message to a GW

; This is an ECHO message if we are sending to a PRIME gateway, or
; an ECHO REPLY addressed to ourself if testing a non-PRIME
; gateway.  Net result is that we get back only ECHO REPLIES.

;GW/	Pointer to gateway block
;
;	CALL SNDPNG
;Ret+1:	Always.

SNDPNG:
IFN MNET,<SAVP1>
	PUSH P,PKT
	PUSH P,CPKT

	LOAD T1,GWTYP,(GW)	; Gateway type code
	CAIN T1,GW%AUP		; Always up?
	 JRST SNDPNU		; Yes, go fake a successful ping

; Must actually send a ping

	MOVEI T1,MINICW		; Size of echo packet
	CALL GETBLK		; Get storage in which to build pkt
	SKIPN PKT,T1		; Put in standard place
	  JRST SNDPNU		; Not available.  Don't let it go down.
	MOVEI T2,MINICW		; Size again
	CALL CLRBLK		; Clear all flags, checksum, etc

	MOVE T1,[BYTE (8)105,0,0,<8+MINIHS>]
	MOVEM T1,PKTELI+.IPKVR(PKT)	; Set version, length
	MOVEI T1,3		; Ping "lifetime"
	STOR T1,PITTL,(PKT)
	MOVEI T1,.ICMFM		; Protocol is ICMP
	STOR T1,PIPRO,(PKT)

	MOVEI CPKT,<<MINIHS+3>/4>+PKTELI ; Min. Internet header size
	ADD CPKT,PKT		; Pointer to ICMP Section

	MOVE T1,.GWILS(GW)	; Get accessable interface
	CALL GWYLUK		; Look it up
	JUMPE T1,SNDPNW		; No way to that net??
IFE MNET,<MOVE T2,NLHOST(T3)>	; Get our address
IFN MNET,<MOVE T2,NTLADR(P1)>	; get interface address
	MOVEI T3,ICM%EC		; Echo type

	LOAD T4,GWTYP,(GW)	; Get type
	CAIN T4,GW%PRM		; Prime?
	 JRST SNDPN6		; Yes

	EXCH T1,T2		; Swap source and destination
	MOVEI T3,ICM%ER		; ECHO-REPLY code
	SETONE PNLCL,(PKT)	; No local delivery allowed
SNDPN6:
	STOR T2,PISH,(PKT)	; Make it look like it came from there
	STOR T1,PIDH,(PKT)	; Make it go there
	STOR T3,CMTYP,(CPKT)	; Set into ICMP section
	SETZRO CMCOD,(CPKT)	; Clear code word
	SETONE CMID,(CPKT)	; (Make field non-zero)
	AOS T1,ICMSID		; Get an Id
	STOR T1,CMSEQ,(CPKT)
	STOR T1,PISID,(PKT)

	CALL ICMCKS		; Compute checksum
	STOR T1,CMCKS,(CPKT)	; Insert in packet

	CALL SNDGAT		; Send it off
	JRST SNDPNX

; Error exits

SNDPNU:	SETZRO GWPIP,(GW)	; Fake a successful ping
	JRST SNDPNX

SNDPNW:	CALL RETPKT		; Don't have anywhere to send packet


SNDPNX:	POP P,CPKT
	POP P,PKT
	RET

;GWDOWN		Gateway just detected down

;		Called by the PINGER and in response to a local net
;		"destination dead" message

;GW/	Pointer to gateway block
;
;	CALL GWDOWN
;Ret+1:	Always.  GWUP bit cleared and all interfaces removed from tables

GWDOWN:	MOVE T1,.GWILS(GW)	; Get the relevant interface
	CALL GWYDWN		; say it went away
	RET

;;	ENDAV.

; ICMDSP	Dispatch on ICMP message type

;
;	CALL ICMDSP
;Ret+1:	Always.


ICMDSP:
IFN MNET,<SAVP1>
	PUSH P,PKT
	PUSH P,CPKT
	PUSH P,TCB
	SETZ TCB,

ICMDS1:	MOVE T1,ICMIPQ		; Pointer to input queue head
	LOAD PKT,QNEXT,(T1)	; Get first thing on queue
	SETSEC PKT,INTSEC	; Make extended address
	CAMN PKT,T1		; Pointer to head means empty
	 JRST ICMDSX		; Empty
	MOVE T1,PKT		; What to dequeue
	CALL DQ			; Get it off queue (NOSKED not needed)

	MOVX T1,PT%CDI		; ICMP dequeued from input queue
	TDNE T1,INTTRC		; Want trace?
	  CALL PRNPKI		; Yes

	CALL ICMCKS		; Check ICMP Checksum
	JUMPN T1,ICMDSC		; Jump if bad

	LOAD CPKT,PIDO,(PKT)	; Internet data offset
	ADD CPKT,PKT		; Get pointer to ICMP portion
	ADDI CPKT,PKTELI	; Skip over local information

	LOAD T1,CMTYP,(CPKT)	; What kind of message it is
	MOVSI T2,-NICMPT	; Number of messages we know about
	CAME T1,ICMTTB(T2)	; Matches this one?
	AOBJN T2,.-1		; No.  Try next.
	JUMPGE T2,ICMDST	; Jump if not found
	LOAD T3,PIPL,(PKT)	; Packet length in bytes
	LOAD T4,PIDO,(PKT)	; Internet data offset in words
	ASH T4,2		; Make that bytes
	SUB T3,T4		; Number of bytes in ICMP part
	SUB T3,ICMMDC(T2)	; Minus min number req'd for this type
	JUMPL T3,ICMDSS		; Enough in packet?
	CALL @ICMRTB(T2)	; Yes, call routine; skips if keeping pkt
ICMDS8:	  CALL RETPKT		; Return the packet to free storage
	JRST ICMDS1		; Loop through rest of Q

; Errors

ICMDSC:	MOVX T1,PT%CKC		; Checksum failure
	JRST ICMDS9

ICMDSS:	MOVX T1,PT%CKS		; Short packet
	JRST ICMDS9

ICMDST:	MOVX T1,PT%CKT		; Unknown type
;	JRST ICMDS9

ICMDS9:	TDNE T1,INTTRC		; Want trace?
	  CALL PRNPKI		; Yes
	AOS BADPCT		; Increment bad packet count
	JRST ICMDS8		; and loop

ICMDSX:	POP P,TCB
	POP P,CPKT
	POP P,PKT
	RET

; Table of type codes (ordered by frequency):

ICMTTB:	ICM%ER			; Echo reply
	ICM%EC			; Echo
	ICM%DU			; Destination unreachable
	ICM%RD			; Redirect output
	ICM%SQ			; Source quench
	ICM%PP			; Parameter problem
	ICM%TE			; Time exceeeded
	ICM%TM			; Time stamp
	ICM%TR			; Time stamp reply
	ICM%IQ			; Information request
	ICM%IR			; Information reply
NICMPT==.-ICMTTB		; Number of types we know about

; Action routines table parallel to the above:

ICMPDU==ICMUSR
ICMPTR==ICMUSR
ICMPIR==ICMUSR

ICMRTB:	IFIW!ICMPER		; process echo reply		
	IFIW!ICMPEC		; process echo
	IFIW!ICMPDU		; process destination unreachable
	IFIW!ICMPRD		; process redirect
	IFIW!ICMUSR		; process source quench
	IFIW!ICMUSR		; process parameter problem
	IFIW!ICMUSR		; process time exceeded
	IFIW!ICMPTM		; process timestamp request
	IFIW!ICMPTR		; process timestamp reply
	IFIW!ICMPIQ		; process information request
	IFIW!ICMPIR		; process information reply
IFN .-ICMRTB-NICMPT,<PRINTX ? ICMP dispatch tables have wrong size>


; Table of minimum data counts (in bytes)
; (if greater than 8+24, then an internet header and 64 bits of data
; is part of the packet)

ICMMDC:	8			; Echo reply length
	8			; Echo length
	8+MINIHS+8		; Destination unreachable
	8+MINIHS+8		; Redirect
	8+MINIHS+8		; Source quench
	8+MINIHS+8		; Parameter problem
	8+MINIHS+8		; Time exceeded
	8+24			; Timestamp request
	8+24			; Timestamp reply
	8			; Information request
	8			; Information reply
IFN .-ICMMDC-NICMPT,<PRINTX ? ICMP tables screwed up>


; Process an ECHO message:

ICMPEC:	MOVX T1,ICM%ER		; Echo reply code
	JRST ICMPEX		; Swap & send


; Process Timestamp request

ICMPTM:	CALL INETUT		; Get universal time
	STOR T1,CMTSR,(CPKT)	; Not really right
	STOR T1,CMTST,(CPKT)	; Transmission time
	MOVX T1,ICM%TR		; Timestamp reply code
	JRST ICMPEX		; Swap & send


; Process Information request

ICMPIQ:	MOVX T1,ICM%IR		; Information reply code
IFE MNET,<MOVE T2,INETID>	; Our default address
IFN MNET,<MOVE T2,DEFADR>
	JRST ICMPEY


; Common exit for replies

ICMPEX:	LOAD T2,PIDH,(PKT)	; Destination (us)
ICMPEY:	LOAD T3,PISH,(PKT)	; Source (who wants echo)
	STOR T1,CMTYP,(CPKT)	; Set as the ICMP type code
	STOR T2,PISH,(PKT)	; We are the echoer
	STOR T3,PIDH,(PKT)	; Sender is the echoee

	SETZRO CMCKS,(CPKT)	; Clear checksum
	CALL ICMCKS		; do checksum
	STOR T1,CMCKS,(CPKT)	; set it
	CALL SNDGAT		; Send it back
	RETSKP

; Process an ECHO-REPLY message:

ICMPER:	MOVX T1,<.RTJST(-1,CMID)> ; We set it to -1
	LOAD T2,CMID,(CPKT)	; What's in packet?
	CAME T1,T2		; Reply for us or a user?
	  JRST ICMUSR		; Not one PINGER sent, give to user

	PUSH P,GW
	LOAD T1,PISH,(PKT)	; Who it appears to be from (maybe us)
	CALL FINDGW		; Look up the gateway block
	JUMPE GW,ICERX		; Not there
	SETZRO GWPIP,(GW)	; Clear ping-in-progress bit
ICERX:	POP P,GW
	RET

; Process a REDIRECT message
; The destination that triggered the message is in the 
; "trigger header"
ICMPRD:	
	ACVAR <GWY>
	LOAD T1,CMCOD,(CPKT)	; get the code
	CAIE T1,RD%NET		; re-direct net?
	 CALLRET ICMUSR		; No, the rest must be handled by the user
	LOAD T1,CMGWA,(CPKT)	; And the correct gateway address
IFN MNET,<
	CALL FNDNCT		; Get the NCT for that net
	 RET			; NO? ignore it
	LOAD T2,NTNUM,(P1)	; Get the interface index
>
IFE MNET,<
PRINTX ? INCMP: ICMPRD needs to find interface index somehow
>
	STOR T2,INTNUM,+T1	; Save in the address
	MOVE GWY,T1		; Save address
	LOAD T1,PIDH,-PKTELI+.CMINH(CPKT); get the triggering destination host
	NETNUM T2,T1		; Get the network number
	CALL NETHSH		; Hash it
	 CAIA			; Not currently in tables (cache flushed)
; Local net unreachable mean partitioned??
	SKIPL NETGWY(T2)	; A local net?
	 MOVEM GWY,NETGWY(T2)	; No, set this as the gateway
	RET			; and return

	PURGE GWY

;;; Process an UNREACHABLE message

;;; This means that all paths into the indicated network (host, port etc.)
;;; are broken. There is no need to change the routing tables,
;;; since they would be re-created anyway, and if the net comes back
;;; via another path we will get a redirect message.
;;; Therefore all we need to do is notify the user (so s/he can break
;;; the connection if s/he wishes)
;ICMPDU: 


; FINDGW	Set GW to point to gateway block with address in T1

;T1/	An interface address
;
;	CALL FINDGW
;Ret+1:	Always.  GW has pointer to block or 0 if not found


FINDGW:	ACVAR <GWX>
	MOVSI GWX,-MAXGWA	; Size of table
FINDG1:	HRRZ GW,GWX		; Get table offset
	ADD GW,GWTAB		; Add base
	SKIPN GW,0(GW)		; Get pointer to gateway block
	 JRST FINDG9		; Empty slot
	CAMN T1,.GWILS(GW)	; This gateway?
	 JRST FINDGX		; yes, exit with it
FINDG9:	AOBJN GWX,FINDG1	; Try next gateway
	MOVEI GW,0		; Indicate failure
FINDGX:	RET

	PURGE GWX
;;	ENDAV.

;;;
;;; ICMUSR -- Give an ICMP message to the user.
;;; There are two possibilities:
;;; If the message is in response to a user Q message,
;;; we simply stick it on that Q's recieve Q (and let the
;;; user process it)
;;; If the message is in response to a monitor protocal (TCP)
;;; then we have to call the proper routines for handling
;;; it.
;;; Called
;;;	PKT/	Packet pointer
;;;	CPKT/	ICMP portion
;;; (at all times, if we reach this routine, the ICMP data
;;; contains the Internet header that triggered the message
;;; starting at .CMINH(CPKT) )
;;; *** NOT FULLY IMPLIMENTATED ****


ICMUSR:	ACVAR <PIX,PTB,PTL>
	MOVEI PTB,INTPIX+1		; Locate tables
	MOVE PIX,INTPIX			; # protocals
	HRRZ PTL,PIX			; Table length
	LOAD T1,PIPRO,-PKTELI+.CMINH(CPKT)	; Get triggering protocal

ICMUS0:	SKIPN .INTPO(PTB)		; Protocal on?
	  JRST ICMUS3			; No, skip it
	SKIPL T2,.INTPL(PTB)		; Take any protocal?
	 CAMN T1,T2			; Or match?
	  CALLRET @.INTPE(PTB)		; Enter the proper routine
;;; Routine returns (to ICMDS8) +2 if it has kept the packet


ICMUS3:	ADD PTB,PTL			; Increment pointer
	AOBJN PIX,ICMUS0		; And loop
;;; Here if nobody to accept the packet
	RET				; Return so it will be released.

;;; Here to process an ICMP message for ICMP
ICMICM::RET				; Shouldn't happen

;;; Dummy routine to handle an ICMP message for TCP
;;; (** remove when implimented **)
TCPICM::RET

	PURGE PIX,PTB,PTL

;;; ICMERR -- Handle an error. Called with
;;;
;;; T1/	ICMP error code (LH == subcode if any)
;;; T2/ Additional info, if any (parameter problem pointer)
;;; PKT/	Erring packet
;;;	CALL ICMERR
;;; Ret+1: Always, packet returned if PINTL and PPROG were both zero


ICMERR::ACVAR	<ICMIDX,INFO>		; AC variables
	TRVAR	<ERROR,HDRSIZ>		; Stack variables
	PUSH	P,CPKT			; Save register we clobber
	SETZ	CPKT,			; Clear
	MOVEM	T1,ERROR		; Save erorr code
	MOVEM	T2,INFO			; and additional info

	LOAD	T2,PIPRO,(PKT)		; Get protocal
	CAIN	T2,.ICMFM		; Internet control message format?
	  JRST	ICMERX			; Yes, Ignore the packet

	HRRZS	T1			; Keep only the ICMP type
	MOVSI	ICMIDX,-NICMPT		; Number of ICMP types we handle
	CAME	T1,ICMTTB(ICMIDX)	; Same?
	  AOBJN	ICMIDX,.-1		; Loop through the table
	JUMPGE	ICMIDX,[INBUG(HLT,<ICMERR -- Bad type code>,ICMBDE)]

	SETZM	HDRSIZ			; Assume don't include header
	MOVX	T1,↑D<8+24>		; Size of data if no header
	CAMLE	T1,ICMMDC(ICMIDX)	; Same?
	  JRST	ICMER0			; Yes, no header, skip next
	LOAD	T1,PIDO,(PKT)		; Get data offset
	ADDI	T1,2			; Plus 64 bits of data
	MOVEM	T1,HDRSIZ		; Remember it (words)
ICMER0:
	MOVX	T1,MINICW		; Buffer size w/o header
	ADD	T1,HDRSIZ		; Plus size of header (if needed)
	JN	<PINTL,PPROG>,(PKT),ICMER1 ; Program still need packet?
	LOAD	T2,PIPL,(PKT)		; This buffer big enough to reuse?
	LSH	T2,-2			; Its size, words, rounded down
	MOVE	T3,HDRSIZ		; Beware BLT over self
;	ADDI	T3,PKTELI		; Last word to be copied
;	CAIG	T3,PKTELI+<<MINIHS+3>/4>+.CMINH	; Vs first word written
	CAIG	T3,<<MINIHS+3>/4>+.CMINH	; Vs first word written
	 CAILE	T1,PKTELI(T2)		; Req'd vs actual
	  CAIA				; Need more than have or overwrite
	   JRST	ICMER2			; Reuse this packet, go shift

ICMER1:	CALL	GETBLK			; Get new packet
	JUMPE	T1,ICMERX		; No storage, do nothing
	SKIPA	CPKT,T1			; Save packet
ICMER2:	  MOVE	CPKT,PKT		; Re-use the packet buffer

; NB: CPKT is not pointing at ICMP header, but at packet

	SKIPN	T1,HDRSIZ		; Include header?
	  JRST	ICMER3			; No, skip next
	XMOVEI	T2,PKTELI(PKT)		; Start of internet leader
	XMOVEI	T3,PKTELI+<<MINIHS+3>/4>+.CMINH(CPKT)	; Where to stash it
	CALL	XBLTA			; move the data
ICMER3:

	LOAD	T1,PISH,(PKT)		; Get the packet source host
	STOR	T1,PIDH,(CPKT)		; Save as error destination
IFE MNET,<MOVE	T1,INETID>		; Get default internet ID
IFN MNET,<MOVE	T1,DEFADR>
	STOR	T1,PISH,(CPKT)		; Save it also
	MOVE	T1,[BYTE (8)105,0,0,0]	; First word of packet
	MOVEM	T1,PKTELI+.IPKVR(CPKT)	; Save
	SETZM	PKTELI+.IPKSG(CPKT)	; clear segmentation info
	MOVE	T1,[BYTE (8)12,.ICMFM,0,0] ; Time to live, protocal
	MOVEM	T1,PKTELI+.IPKPR(CPKT)	; Save it
	MOVE	T1,HDRSIZ		; Get header size included
	ADDI	T1,<<MINIHS+3>/4>+.CMINH ; Plus Internet & ICMP header
	ASH	T1,2			; Convert to bytes
	STOR	T1,PIPL,(CPKT)		; Save packet length

	PUSH	P,PKT			; Save old packet	
	PUSH	P,CPKT			; ...
	MOVE	PKT,CPKT		; Get new
	ADDI	CPKT,PKTELI+<MINIHS+3>/4 ; Point to the ICMP section

	HRRZ	T1,ERROR		; Get error type
	STOR	T1,CMTYP,(CPKT)		; Save type	
	HLRZ	T2,ERROR		; Get code
	STOR	T2,CMCOD,(CPKT)		; Save it
	SETZM	1(CPKT)			; Unused word

	CAIN T1,ICM%PP			; Parameter problem?
	 CAIE T2,PP%PTR			; With pointer?
	  JRST ICMER5			; No
	STOR INFO,CMPTR,(CPKT)		; Yes, set pointer
	JRST ICMER9
ICMER5:
	CAME T1,ICM%TM			; Time request?
	  JRST ICMER6			; No
	CALL INETUT			; Get time
	STOR T1,CMTSO,(CPKT)
	SETZRO CMTSR,(CPKT)
	SETZRO CMTST,(CPKT)
	MOVX T1,<MINIHS+5*4>
	LOAD T1,PIPL,(PKT)
;	STOR T1,PIPL,(PKT)
	JRST ICMER9
ICMER6:

ICMER9:	AOS	T1,ICMSID		; Get an Id
	STOR	T1,CMSEQ,(CPKT)
	STOR	T1,PISID,(PKT)

	CALL	ICMCKS			; Checksum the packet
	STOR	T1,CMCKS,(CPKT)		; Save it also
	CALL	SNDGAT			; Send it off
	POP	P,CPKT			; Restore old contents
	POP	P,PKT			; Restore old contents

ICMERX:	JN	<PINTL,PPROG>,(PKT),ICMERZ ; Return without destroying packet
	CAME	PKT,CPKT		; Don't release if re-used
	  CALL	RETPKT			; and return storage
ICMERZ:
	POP P,CPKT			; restore
	RET				; return

	PURGE ICMIDX,ERROR,INFO,HDRSIZ

	TNXEND
	END